package top.lingkang.finalgateway.admin.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.MethodType;
import org.noear.solon.core.handle.ModelAndView;
import org.noear.solon.core.handle.UploadedFile;
import top.lingkang.finalgateway.FinalGateway;
import top.lingkang.finalgateway.admin.constants.CommonValue;
import top.lingkang.finalgateway.admin.constants.LogType;
import top.lingkang.finalgateway.admin.dto.ResponseResult;
import top.lingkang.finalgateway.admin.dto.ResponseResultPage;
import top.lingkang.finalgateway.admin.entity.SslEntity;
import top.lingkang.finalgateway.admin.mapper.ActionLogMapper;
import top.lingkang.finalgateway.admin.mapper.SslMapper;
import top.lingkang.finalgateway.admin.param.AddSslParam;
import top.lingkang.finalgateway.admin.param.SslListParam;
import top.lingkang.finalgateway.admin.param.UpdateSslParam;
import top.lingkang.finalgateway.admin.vo.SslEntityVO;
import top.lingkang.finalgateway.admin.vo.SslInfo;
import top.lingkang.finalgateway.bean.CertificateInfo;
import top.lingkang.finalgateway.config.RouterConfig;
import top.lingkang.finalgateway.utils.SslUtils;
import top.lingkang.finalvalidated.core.ValidObject;
import top.lingkang.mm.page.PageHelper;
import top.lingkang.mm.page.PageInfo;
import top.lingkang.mm.solon.Transactional;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.cert.CertificateException;
import java.util.List;

/**
 * @author lingkang
 * @create by 2024/5/6 17:07
 */
@Mapping("/api/ssl")
@Controller
@Slf4j
public class SslController {
    @Inject
    private SslMapper sslMapper;
    @Inject
    private ActionLogMapper actionLogMapper;

    @Mapping(value = "/index", method = MethodType.GET)
    public Object ssl() {
        return new ModelAndView("view/ssl.ftl");
    }

    @Mapping(value = "/list", method = MethodType.GET)
    public Object list(@ValidObject SslListParam param) {
        if (StrUtil.isNotBlank(param.getName())) {
            param.setName("%" + param.getName() + "%");
        }
        PageHelper.startPage(param.getPage(), param.getSize());
        List<SslEntity> list = sslMapper.list(param);
        PageInfo page = PageHelper.getPage();
        return new ResponseResultPage<>().setData(list)
                .setPage(param.getPage())
                .setSize(param.getSize())
                .setTotal(page.getTotal());
    }

    @Transactional
    @Mapping(value = "/add", method = MethodType.POST)
    public synchronized Object add(@ValidObject AddSslParam param, UploadedFile file) throws IOException {
        Assert.isTrue(CommonValue.ssl_type.contains(param.getType()), "未知类型: " + param.getType());
        Assert.isFalse("0".equals(param.getName()), "名称不能为 0");
        Assert.isFalse(sslMapper.existsById(param.getName()), "名称存在重复: " + param.getName());
        if ("openssl".equals(param.getType())) {
            Assert.notBlank(param.getPublicKey(), "公钥不能为空");
            Assert.notBlank(param.getPrivateKey(), "私钥不能为空");
            CertificateInfo info = null;
            try {
                info = SslUtils.readCertificateInfo(param.getPublicKey());
            } catch (CertificateException e) {
                log.warn("加载公钥失败，证书错误" + e.getMessage(), e);
                return new ResponseResult<>().fail("加载公钥失败，证书错误");
            }
            try {
                SslContextBuilder forServer = SslContextBuilder.forServer(
                        new ByteArrayInputStream(param.getPublicKey().getBytes(StandardCharsets.UTF_8)),
                        new ByteArrayInputStream(param.getPrivateKey().getBytes(StandardCharsets.UTF_8)),
                        StrUtil.isBlank(param.getPassword()) ? null : param.getPassword().trim()
                );
                SslContext sslContext = forServer.build();
                SSLEngine engine = sslContext.newEngine(null);
                SslEntity entity = new SslEntity();
                entity.setName(param.getName());
                entity.setType(param.getType());
                entity.setDomain(SslUtils.getDomain(info.getDomain()));
                entity.setStartTime(info.getStartTime());
                entity.setEndTime(info.getEndTime());

                entity.setPassword(param.getPassword());
                entity.setPublicKey(param.getPublicKey().getBytes(StandardCharsets.UTF_8));
                entity.setPrivateKey(param.getPrivateKey().getBytes(StandardCharsets.UTF_8));
                entity.setProtocols(ONode.load(engine.getSupportedProtocols()).toJson());
                entity.setEnableProtocols(ONode.load(engine.getEnabledProtocols()).toJson());
                entity.setCipherSuites(ONode.load(engine.getSupportedCipherSuites()).toJson());
                entity.setEnableCipherSuites(ONode.load(engine.getEnabledCipherSuites()).toJson());
                entity.setRemarks(param.getRemarks());
                sslMapper.insert(entity);
                actionLogMapper.addLog(LogType.ssl_add, ONode.load(entity).toJson());
            } catch (Exception e) {
                log.warn("加载私钥失败，" + e.getMessage(), e);
                return new ResponseResult<>().fail("加载私钥失败，不是PKCS#8，证书错误");
            }
        } else {
            Assert.notNull(file, "jks证书文件不能为空");
            File jks = File.createTempFile("jks", ".jks");
            File jskFile = FileUtil.writeFromStream(file.getContent(), jks);
            file.delete();
            try {
                SSLContext sslContext = SslUtils.getSslContext(param.getPassword(), Files.newInputStream(jskFile.toPath()));
                SSLEngine engine = sslContext.createSSLEngine();
                SslInfo info = SslUtils.getJksDomain(param.getPassword(), Files.newInputStream(jskFile.toPath()));
                SslEntity entity = new SslEntity();
                entity.setName(param.getName());
                entity.setType(param.getType());
                entity.setDomain(SslUtils.getDomain(info.getDomain()));
                entity.setStartTime(info.getStartTime());
                entity.setEndTime(info.getEndTime());

                entity.setPassword(param.getPassword());
                byte[] jskBytes = IoUtil.read(Files.newInputStream(jskFile.toPath())).toByteArray();
                entity.setPublicKey(jskBytes);
                entity.setProtocols(ONode.load(engine.getSupportedProtocols()).toJson());
                entity.setEnableProtocols(ONode.load(engine.getEnabledProtocols()).toJson());
                entity.setCipherSuites(ONode.load(engine.getSupportedCipherSuites()).toJson());
                entity.setEnableCipherSuites(ONode.load(engine.getEnabledCipherSuites()).toJson());
                entity.setRemarks(param.getRemarks());
                sslMapper.insert(entity);
                actionLogMapper.addLog(LogType.ssl_add, ONode.load(entity).toJson());
            } catch (Exception e) {
                log.warn("加载jks证书失败，" + e.getMessage(), e);
                if (e.getMessage().contains("Get Key failed") || e.getMessage().contains("password was incorrect")) {
                    return new ResponseResult<>().fail("加载jks证书失败，密码错误");
                }
                return new ResponseResult<>().fail("加载jks证书失败，" + e.getMessage());
            } finally {
                jskFile.delete();
            }
        }
        return new ResponseResult<>().ok();
    }

    @Transactional
    @Mapping(value = "/update", method = MethodType.POST)
    public Object update(@ValidObject UpdateSslParam param, UploadedFile file) {
        Assert.isFalse("0".equals(param.getName()), "名称不能为 0");
        SslEntity entity = sslMapper.selectById(param.getName());
        Assert.notNull(entity, "更新数据不存在");

        List<String> list = ONode.loadStr(param.getEnableProtocols()).toObjectList(String.class);
        Assert.isTrue(list != null && !list.isEmpty(), "启用的协议不能为空");
        list = ONode.loadStr(param.getEnableCipherSuites()).toObjectList(String.class);
        Assert.isTrue(list != null && !list.isEmpty(), "启用的加密算法不能为空");

        if ("openssl".equals(entity.getType())) {
            Assert.notBlank(param.getPublicKey(), "公钥不能为空");
            Assert.notBlank(param.getPrivateKey(), "私钥不能为空");
            // 检测证书
            if (!param.getPublicKey().equals(entity.getPublicKey())) {
                CertificateInfo info = null;
                try {
                    info = SslUtils.readCertificateInfo(param.getPublicKey());
                } catch (CertificateException e) {
                    return new ResponseResult<>().fail("公钥校验失败，证书错误");
                }
                entity.setPublicKey(param.getPublicKey().getBytes(StandardCharsets.UTF_8));
                entity.setDomain(SslUtils.getDomain(info.getDomain()));
                entity.setStartTime(info.getStartTime());
                entity.setEndTime(info.getEndTime());
            } else {
                entity.setEnableProtocols(param.getEnableProtocols());
                entity.setEnableCipherSuites(param.getEnableCipherSuites());
            }
            if (!param.getPrivateKey().equals(entity.getPrivateKey())) {
                try {
                    SslContextBuilder forServer = SslContextBuilder.forServer(
                            new ByteArrayInputStream(param.getPublicKey().getBytes(StandardCharsets.UTF_8)),
                            new ByteArrayInputStream(param.getPrivateKey().getBytes(StandardCharsets.UTF_8)),
                            StrUtil.isBlank(param.getPassword()) ? null : param.getPassword().trim()
                    );
                    SslContext build = forServer.build();
                    SSLEngine engine = build.newEngine(null);
                    // 属性
                    entity.setProtocols(ONode.load(engine.getSupportedProtocols()).toJson());
                    entity.setEnableProtocols(ONode.load(engine.getEnabledProtocols()).toJson());
                    entity.setCipherSuites(ONode.load(engine.getSupportedCipherSuites()).toJson());
                    entity.setEnableCipherSuites(ONode.load(engine.getEnabledCipherSuites()).toJson());
                } catch (Exception e) {
                    return new ResponseResult<>().fail("私钥校验失败，不是PKCS#8，证书错误");
                }
                entity.setPrivateKey(param.getPrivateKey().getBytes(StandardCharsets.UTF_8));
            } else {
                entity.setEnableProtocols(param.getEnableProtocols());
                entity.setEnableCipherSuites(param.getEnableCipherSuites());
            }
        } else {
            if (file != null) {
                try {
                    File jks = File.createTempFile("jks", ".jks");
                    File jskFile = FileUtil.writeFromStream(file.getContent(), jks);
                    file.delete();
                    SSLContext sslContext = SslUtils.getSslContext(param.getPassword(), Files.newInputStream(jskFile.toPath()));
                    SSLEngine engine = sslContext.createSSLEngine();
                    SslInfo info = SslUtils.getJksDomain(param.getPassword(), Files.newInputStream(jskFile.toPath()));
                    // 公钥
                    byte[] jskBytes = IoUtil.read(Files.newInputStream(jskFile.toPath())).toByteArray();
                    entity.setPublicKey(jskBytes);
                    entity.setDomain(SslUtils.getDomain(info.getDomain()));
                    entity.setStartTime(info.getStartTime());
                    entity.setEndTime(info.getEndTime());
                    // 属性
                    entity.setProtocols(ONode.load(engine.getSupportedProtocols()).toJson());
                    entity.setEnableProtocols(ONode.load(engine.getEnabledProtocols()).toJson());
                    entity.setCipherSuites(ONode.load(engine.getSupportedCipherSuites()).toJson());
                    entity.setEnableCipherSuites(ONode.load(engine.getEnabledCipherSuites()).toJson());
                } catch (Exception e) {
                    log.warn("加载jks证书失败，" + e.getMessage(), e);
                    if (e.getMessage().contains("password was incorrect")) {
                        return new ResponseResult<>().fail("加载jks证书失败，密码错误");
                    }
                    return new ResponseResult<>().fail("加载jks证书失败，" + e.getMessage());
                }
            } else {
                entity.setEnableProtocols(param.getEnableProtocols());
                entity.setEnableCipherSuites(param.getEnableCipherSuites());
                // 如果密码变动
                if (entity.getPassword() != null && !entity.getPassword().equals(param.getPassword())) {
                    try {
                        SslUtils.getJksDomain(param.getPassword(), new ByteArrayInputStream(entity.getPublicKey()));
                    } catch (Exception e) {
                        return new ResponseResult<>().fail("jks密码错误");
                    }
                }
            }
        }
        entity.setPassword(param.getPassword());
        entity.setRemarks(param.getRemarks());

        sslMapper.updateById(entity);
        actionLogMapper.addLog(LogType.ssl_update, ONode.load(param).toJson());
        return new ResponseResult<>().ok();
    }

    @Transactional
    @Mapping(value = "/delete", method = MethodType.POST)
    public Object delete(String name) {
        Assert.notBlank(name, "名称不能为空");
        for (RouterConfig config : FinalGateway.config.getRouterConfigs()) {
            if (name.equals(config.getSsl()))
                return new ResponseResult<>().fail("删除失败，证书已经被引用：" + name);
        }
        Assert.isTrue(sslMapper.existsById(name), "数据不存在");
        sslMapper.deleteById(name);
        actionLogMapper.addLog(LogType.ssl_delete, name);
        return new ResponseResult<>().ok();
    }

    @Mapping(value = "/get", method = MethodType.GET)
    public Object get(String name) {
        Assert.notBlank(name, "名称不能为空");
        SslEntity entity = sslMapper.selectById(name);
        Assert.notNull(entity, "数据不存在");
        if ("jks".equals(entity.getType())) {
            entity.setPublicKey(null);
        }
        SslEntityVO vo = new SslEntityVO();
        BeanUtil.copyProperties(entity, vo);
        if ("openssl".equals(entity.getType())) {
            if (entity.getPublicKey() != null)
                vo.setPublicKey(new String(entity.getPublicKey(), StandardCharsets.UTF_8));
            if (entity.getPrivateKey() != null)
                vo.setPrivateKey(new String(entity.getPrivateKey(), StandardCharsets.UTF_8));
        }
        return new ResponseResult<>(vo);
    }
}
